package app

import (
	"fmt"
	kahttpreq "kiwi/actor/net/http/request"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"time"

	aactor "github.com/asynkron/protoactor-go/actor"
	"github.com/spf13/viper"

	kactor "kiwi/actor"
	kadefine "kiwi/actor/define"
	katimer "kiwi/actor/timer"
	kawork "kiwi/actor/work"
	kdebug "kiwi/debug"
	klog "kiwi/log"
	kutil "kiwi/util"
)

type App struct {
	startedOnce  sync.Once
	startedWg    sync.WaitGroup
	stoppingOnce sync.Once
	stoppedOnce  sync.Once
	stoppedWg    sync.WaitGroup

	config *viper.Viper

	name string

	logger klog.ILogger

	pid *kadefine.PID

	timerMgr *katimer.Manager

	httpRequestMgr *kahttpreq.Manager
}

func Create(configFilePath string) (*App, error) {
	// 加载配置
	config := viper.New()
	configDir, configName := filepath.Split(configFilePath)
	configName = kutil.TruncateExtend(configName)
	configType := strings.TrimLeft(path.Ext(configFilePath), ".")
	config.SetConfigName(configName)
	config.SetConfigType(configType)
	config.AddConfigPath(configDir)
	err := config.ReadInConfig()
	if err != nil {
		return nil, err
	}

	if !config.InConfig("ID") {
		return nil, fmt.Errorf("must config id")
	}

	id := config.GetString("ID")

	name := configName + "-" + id

	// 初始化日志
	var logger klog.ILogger
	if config.InConfig("Logger") {
		logger, err = CreateLogger(name, config.Sub("Logger"), config.Sub("LogFile"))
		if err != nil {
			return nil, err
		}
	} else {
		logger = klog.Default()
	}

	decider := kactor.StopDecider
	if config.InConfig("decider") {
		deciderName := strings.ToLower(config.GetString("decider"))
		logger.Infof("decider=%s", deciderName)
		switch deciderName {
		case "stop":
			decider = kactor.StopDecider
		case "resume":
			decider = kactor.ResumeDecider
		case "restart":
			decider = kactor.RestartDecider
		default:
			return nil, fmt.Errorf("unknown decider %s", deciderName)
		}
	}

	a := &App{
		config: config,
		name:   name,
		logger: logger,
	}

	a.startedWg.Add(1)

	props := aactor.PropsFromProducer(func() aactor.Actor { return a }, /*aactor.WithMailbox(aactor.Unbounded()),*/
		aactor.WithSupervisor(aactor.NewAllForOneStrategy(0, 0, decider)))
	_, err = kactor.RootContext().SpawnNamed(props, a.name)
	if err != nil {
		return nil, fmt.Errorf("spawn app fail, name=%s, %v", a.name, err)
	}

	a.startedWg.Wait()

	a.logger.Infof("%s started", a.name)

	// 启动接受ctrl+c命令的协程
	go func() {
		kutil.WaitExitSignal()

		a.logger.Info("receive exit signal")

		//for i := len(s.services) - 1; i >= 0; i-- {
		//	svc := s.services[i]
		//	svc.Stop()
		//	if s.cfg.StopServiceIntervalMillisecond > 0 {
		//		time.Sleep(s.cfg.StopServiceIntervalMillisecond * time.Millisecond)
		//	}
		//}

		//if s.cfg.ExitWaitSecond > 0 {
		//	time.Sleep(s.cfg.ExitWaitSecond * time.Second)
		//}

		kactor.Poison(a.pid)
	}()

	return a, nil
}

func MustCreate(configPath string) *App {
	a, err := Create(configPath)
	if err != nil {
		panic(err)
	}
	return a
}

func (a *App) WaitForStopped() {
	a.stoppedWg.Wait()
}

func (a *App) Config() *viper.Viper {
	return a.config
}

func (a *App) Logger() klog.ILogger {
	return a.logger
}

func (a *App) PID() *kadefine.PID {
	return a.pid
}

func (a *App) NewTimer(dur time.Duration, tag katimer.Tag, cb katimer.Callback) katimer.ID {
	return a.timerMgr.AddOnce(dur, tag, cb)
}

func (a *App) NewLoopTimer(dur time.Duration, tag katimer.ID, cb katimer.Callback) katimer.ID {
	return a.timerMgr.AddLoop(dur, tag, cb)
}

func (a *App) StopTimer(id katimer.ID) error {
	return a.timerMgr.Stop(id)
}

func (a *App) HttpRequest(timeout time.Duration, tag kahttpreq.Tag, onPrepareFunc kahttpreq.OnPrepareFunc, onCompletedFunc kahttpreq.OnCompletedFunc) (id kahttpreq.ID, err error) {
	return a.httpRequestMgr.Request(timeout, tag, onPrepareFunc, onCompletedFunc)
}

func (a *App) Receive(ctx kadefine.Context) {
	switch msg := ctx.Message().(type) {
	case *kawork.MessagePostWork:
		msg.Work.Execute(ctx)
		kawork.DestroyMessagePostWork(msg)

	case *kawork.MessageDispatchWork:
		msg.Err = msg.Work.Execute(ctx)
		ctx.Respond(msg)

	case *katimer.MessageTimeout:
		defer katimer.DestroyMessageTimeout(msg)

		err := a.timerMgr.Trigger(ctx, msg.ID)
		if err != nil {
			a.logger.Errorf("[%s] trigger timer fail, id=%d, tag=%d, %s", a.pid.Id, msg.ID, msg.Tag, err)
			return
		}

	case *kadefine.Started:
		a.startedOnce.Do(func() {
			defer func() {
				if p := recover(); p != nil {
					kdebug.WriteStackTraceFile(p, "")
					ctx.Stop(ctx.Self())
				}

				a.stoppedWg.Add(1)

				a.startedWg.Done()
			}()

			a.pid = ctx.Self()

			a.timerMgr = katimer.CreateManager(ctx)

			a.httpRequestMgr = kahttpreq.CreateManager(ctx, a.logger)
		})

	case *kadefine.Stopping:
		a.stoppingOnce.Do(func() {
			defer func() {
				if p := recover(); p != nil {
					kdebug.WriteStackTraceFile(p, "")
				}
			}()
		})

	case *kadefine.Stopped:
		defer func() {
			if p := recover(); p != nil {
				kdebug.WriteStackTraceFile(p, "")
			}

			if a.timerMgr != nil {
				a.timerMgr.StopAll()
				a.timerMgr = nil
			}

			a.logger.Infof("%s stopped", a.name)

			a.stoppedWg.Done()
		}()
	}
}
